Skip to content

Conversation

@jenny-s51
Copy link
Contributor

What: Closes #11962

This PR enables support for AnimationsProvider.

PatternFly consumers will now be able to set animations for opt-in components once at the app level (instead of on every component via hasAnimations) while maintaining full backward compatibility.

Testing Instructions

To test the new context provider functionality:

  1. Run yarn start and navigate to http://localhost:8002/components/alert#toast-alert-group

  2. Copy and paste the code below into the demo TSX code:

    Click to expand
     // eslint-disable-next-line no-restricted-imports
     import React, { Fragment, useState } from 'react';
     import {
       Alert,
       AlertProps,
       AlertGroup,
       AlertActionCloseButton,
       AlertVariant,
       InputGroup,
       InputGroupItem
     } from '@patternfly/react-core';
     import buttonStyles from '@patternfly/react-styles/css/components/Button/button';
     import { AnimationsProvider } from '@patternfly/react-core/dist/esm/helpers';
     export const AlertGroupToast: React.FunctionComponent = () => {
       const [alerts, setAlerts] = useState<Partial<AlertProps>[]>([]);
     
       const addAlert = (title: string, variant: AlertProps['variant'], key: React.Key) => {
         setAlerts((prevAlerts) => [{ title, variant, key }, ...prevAlerts]);
       };
     
       const removeAlert = (key: React.Key) => {
         setAlerts((prevAlerts) => [...prevAlerts.filter((alert) => alert.key !== key)]);
       };
     
       const btnClasses = [buttonStyles.button, buttonStyles.modifiers.secondary].join(' ');
     
       const getUniqueId = () => new Date().getTime();
     
       const addSuccessAlert = () => {
         addAlert('Toast success alert', 'success', getUniqueId());
       };
     
       const addDangerAlert = () => {
         addAlert('Toast danger alert', 'danger', getUniqueId());
       };
     
       const addInfoAlert = () => {
         addAlert('Toast info alert', 'info', getUniqueId());
       };
     
       return (
         <Fragment>
           <InputGroup style={{ marginBottom: '16px' }}>
             <InputGroupItem>
               <button onClick={addSuccessAlert} type="button" className={btnClasses}>
                 Add toast success alert
               </button>
             </InputGroupItem>
             <InputGroupItem>
               <button onClick={addDangerAlert} type="button" className={btnClasses}>
                 Add toast danger alert
               </button>
             </InputGroupItem>
             <InputGroupItem>
               <button onClick={addInfoAlert} type="button" className={btnClasses}>
                 Add toast info alert
               </button>
             </InputGroupItem>
           </InputGroup>
           <AnimationsProvider config={{ hasAnimations: true }}>
             <AlertGroup isToast isLiveRegion>
               {alerts.map(({ key, variant, title }) => (
                 <Alert
                   variant={AlertVariant[variant]}
                   title={title}
                   actionClose={
                     <AlertActionCloseButton
                       title={title as string}
                       variantLabel={`${variant} alert`}
                       onClose={() => removeAlert(key)}
                     />
                   }
                   key={key}
                 />
               ))}
             </AlertGroup>
           </AnimationsProvider>
         </Fragment>
       );
     };
    
  1. Verify the animations are applied to the component via the context provider rather than with the hasAnimations prop.

Additional Changes & Fixes

Converted class components to functional components to enable React Hooks usage useHasAnimations hook integration:

  • AlertGroup.tsx - Required for useHasAnimations hook integration
  • DualListSelector.tsx - Required for useHasAnimations hook integration

Quick Verification

  1. Go to any example for a component that uses the hasAnimations prop.
  2. Wrap with <AnimationsProvider config={{ hasAnimations: true }}>
  3. Remove the hasAnimations prop from the child component.
  4. Verify animations work as expected.

Testing Note: Since PatternFly example files don't typically import from /helpers, the ESLint configs require a React import with the // eslint-disable-next-line no-restricted-imports comment at the top of the file for local testing. Real consumers won't have this issue.

Success Criteria

  • ✅ Component hasAnimations prop overrides provider when set
  • ✅ Existing examples work unchanged without provider
  • ✅ No console errors or TypeScript issues

@jenny-s51 jenny-s51 marked this pull request as draft September 4, 2025 14:49
@patternfly-build
Copy link
Contributor

patternfly-build commented Sep 4, 2025

@jenny-s51 jenny-s51 marked this pull request as ready for review September 4, 2025 16:17
…ons, add AnimationsProvider to helpers, add md files for documentation

fix(components): update hasAnimations to fall back to context

fix(md): remove cssPrefix

fix(alertgroup): fix tests
Copy link
Contributor

@wise-king-sullyman wise-king-sullyman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work on this! I have a handful of comments/change requests but they're mostly fairly small things.

This review didn't include looking at changing AlertGroup and DualListSelector from class based to function components that was needed as unfortunately I ran out of time. I wouldn't say to redo anything, but just for the sake of the future if a similar situation arises I would advocate for splitting that work into a distinct PR just to make review/testing easier, but that's just my $0.02.

});

// Animation context tests
test('respects AnimationsProvider context when no local hasAnimations prop', () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kind of the opposite of Austin's comment about regarding a describe block, I personally might wrap these tests in a describe block specifically for the context tests. Not a blocker and depending how quickly we want to get this merged in, would like to hear @wise-king-sullyman opinion here.

Copy link
Contributor Author

@jenny-s51 jenny-s51 Sep 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think so too @thatblindgeye - based on the RTL guide this seems like a good use case for a grouped describe() block, I've updated the tests to include it, but will also defer to @wise-king-sullyman here for the final word on whether or not we'd like to keep it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not against grouping associated things like the animation tests together in a describe block in an otherwise broader test suite, just wrapping full test suites in a describe block 🙂

target.removeChild(this.state.container);
}
}
appendTo, // do not pass down to ul
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: it's pre-existing but I think we could remove this comment now that it's a functional component.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice catch, removed

const [focusAfterExpandChange, setFocusAfterExpandChange] = useState(false);

const { isExpanded, onToggleExpand, toggleAriaLabel, hasAnimations } = expandableInput || {};
const { isExpanded, onToggleExpand, toggleAriaLabel, hasAnimations: localHasAnimations } = expandableInput || {};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just as a followup to my above reply to Austin about the prop name, I think this makes sense to call it localHasAnimations.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just following up here to confirm based on the discussion in the thread here, I renamed it to hasAnimations for both the destructed and internal variable for consistency with our existing conventions for destructured props, for example JumpLinks uses activeIndex: activeIndexProp and isExpanded: isExpandedProp. What are your thoughts on using hasAnimationsProp here?

Copy link
Contributor

@wise-king-sullyman wise-king-sullyman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀

@wise-king-sullyman wise-king-sullyman merged commit fdd6433 into patternfly:main Sep 23, 2025
13 of 15 checks passed
@patternfly-build
Copy link
Contributor

Your changes have been released in:

Thanks for your contribution! 🎉

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Context Provider - Implement AnimationContextProvider

5 participants